{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "635d8ebb",
   "metadata": {},
   "source": [
    "# Human-in-the-loop\n",
    "\n",
    "- Author: [Jaemin Hong](https://github.com/geminii01)\n",
    "- Peer Review: [Hye-yoon Jeong](https://github.com/Hye-yoonJeong), [JeongHo Shin](https://github.com/ThePurpleCollar), [Chaeyoon Kim](https://github.com/chaeyoonyunakim)\n",
    "- Proofread : [Chaeyoon Kim](https://github.com/chaeyoonyunakim)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/01-Core-Features/06-LangGraph-Human-in-the-loop.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/01-Core-Features/06-LangGraph-Human-in-the-loop.ipynb)\n",
    "## Overview\n",
    "\n",
    "This tutorial covers **Human in the loop** .\n",
    "\n",
    "Agents are not always reliable, and human intervention may be required to ensure tasks are executed successfully. \n",
    "\n",
    "In some cases, you might want human approval before proceeding to confirm that everything is functioning as intended. LangGraph supports these **Human-in-the-loop** workflows. \n",
    "\n",
    "In this tutorial, you'll learn how to use LangGraph's ```interrupt_before``` feature to pause execution, enabling human oversight and control.\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [Function Setup](#function-setup)\n",
    "- [Graph Setup](#graph-setup)\n",
    "- [Adding Human Feedback](#adding-human-feedback)\n",
    "\n",
    "### References\n",
    "\n",
    "- [Tutorials > Quick Start > Human-in-the-loop](https://langchain-ai.github.io/langgraph/tutorials/introduction/#part-4-human-in-the-loop)\n",
    "- [Concepts > LangGraph > Human-in-the-loop](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/)\n",
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6c7aba4",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "21943adb",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f25ec196",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langsmith\",\n",
    "        \"langgraph\",\n",
    "        \"langchain_core\",\n",
    "        \"langchain_openai\",\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "7f9065ea",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Environment variables have been set successfully.\n"
     ]
    }
   ],
   "source": [
    "# Set environment variables\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "set_env(\n",
    "    {\n",
    "        \"OPENAI_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "        \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "        \"LANGCHAIN_PROJECT\": \"06-LangGraph-Human-in-the-loop\",\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "690a9ae0",
   "metadata": {},
   "source": [
    "You can alternatively set API keys such as ```OPENAI_API_KEY``` in a ```.env``` file and load them.\n",
    "\n",
    "[Note] This is not necessary if you've already set the required API keys in previous steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "4f99b5b6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Load API keys from .env file\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c61acea8",
   "metadata": {},
   "source": [
    "## Function Setup\n",
    "\n",
    "Convenient functions for smooth execution."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "323d6d97",
   "metadata": {},
   "outputs": [],
   "source": [
    "import feedparser\n",
    "from urllib.parse import quote\n",
    "from typing import List, Dict, Optional\n",
    "\n",
    "\n",
    "class GoogleNews:\n",
    "    \"\"\"\n",
    "    A class to search Google News.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        Initializes the base URL.\n",
    "        \"\"\"\n",
    "        self.base_url = \"https://news.google.com/rss\"\n",
    "\n",
    "    def _fetch_news(self, url: str, k: int = 3) -> List[Dict[str, str]]:\n",
    "        \"\"\"\n",
    "        Fetches news from the given URL.\n",
    "        \"\"\"\n",
    "        news_data = feedparser.parse(url)\n",
    "        return [\n",
    "            {\"title\": entry.title, \"link\": entry.link}\n",
    "            for entry in news_data.entries[:k]\n",
    "        ]\n",
    "\n",
    "    def _collect_news(self, news_list: List[Dict[str, str]]) -> List[Dict[str, str]]:\n",
    "        \"\"\"\n",
    "        Organizes and returns the news list.\n",
    "        \"\"\"\n",
    "        if not news_list:\n",
    "            print(\"No news found for the given keyword.\")\n",
    "            return []\n",
    "\n",
    "        result = []\n",
    "        for news in news_list:\n",
    "            result.append({\"url\": news[\"link\"], \"content\": news[\"title\"]})\n",
    "\n",
    "        return result\n",
    "\n",
    "    def search_latest(self, k: int = 3) -> List[Dict[str, str]]:\n",
    "        \"\"\"\n",
    "        Searches for the latest news.\n",
    "        \"\"\"\n",
    "        url = f\"{self.base_url}?hl=en&gl=US&ceid=US:en\"\n",
    "        news_list = self._fetch_news(url, k)\n",
    "        return self._collect_news(news_list)\n",
    "\n",
    "    def search_by_keyword(\n",
    "        self, keyword: Optional[str] = None, k: int = 3\n",
    "    ) -> List[Dict[str, str]]:\n",
    "        \"\"\"\n",
    "        Searches for news by keyword.\n",
    "        \"\"\"\n",
    "        if keyword:\n",
    "            encoded_keyword = quote(keyword)\n",
    "            url = f\"{self.base_url}/search?q={encoded_keyword}&hl=en&gl=US&ceid=US:en\"\n",
    "        else:\n",
    "            url = f\"{self.base_url}?hl=en&gl=US&ceid=US:en\"\n",
    "        news_list = self._fetch_news(url, k)\n",
    "        return self._collect_news(news_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5ee82bae",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import BaseMessage\n",
    "\n",
    "\n",
    "def pretty_print_messages(messages: list[BaseMessage]):\n",
    "    for message in messages:\n",
    "        message.pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07f6664a",
   "metadata": {},
   "source": [
    "## Graph Setup\n",
    "\n",
    "This setup initializes a graph-based workflow for a chatbot, defining the state, tools, and nodes while establishing edges and a memory saver for persistent interactions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "c52807f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated\n",
    "from typing_extensions import TypedDict\n",
    "\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.tools import tool\n",
    "\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.graph.message import add_messages\n",
    "from langgraph.prebuilt import ToolNode, tools_condition\n",
    "\n",
    "\n",
    "###### Define the State ######\n",
    "class State(TypedDict):\n",
    "    messages: Annotated[list, add_messages]\n",
    "\n",
    "\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "\n",
    "###### Define the tool and binding ######\n",
    "@tool\n",
    "def search_keyword(query: str) -> List[Dict[str, str]]:\n",
    "    \"\"\"\n",
    "    Look up news by keyword.\n",
    "    \"\"\"\n",
    "    news_tool = GoogleNews()\n",
    "    return news_tool.search_by_keyword(query, k=5)\n",
    "\n",
    "\n",
    "tools = [search_keyword]\n",
    "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
    "llm_with_tools = llm.bind_tools(tools)\n",
    "\n",
    "\n",
    "###### Add Nodes ######\n",
    "def chatbot(state: State):\n",
    "    \"\"\"\n",
    "    Invoke the message and return the result.\n",
    "    \"\"\"\n",
    "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
    "\n",
    "\n",
    "# chatbot node\n",
    "graph_builder.add_node(\"chatbot\", chatbot)\n",
    "\n",
    "# tool node\n",
    "tool_node = ToolNode(tools=tools)\n",
    "graph_builder.add_node(\"tools\", tool_node)\n",
    "\n",
    "# conditional edge\n",
    "graph_builder.add_conditional_edges(\"chatbot\", tools_condition)\n",
    "\n",
    "\n",
    "###### Add Edges ######\n",
    "# tools -> chatbot\n",
    "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
    "\n",
    "# START -> chatbot\n",
    "graph_builder.add_edge(START, \"chatbot\")\n",
    "\n",
    "# chatbot -> END\n",
    "graph_builder.add_edge(\"chatbot\", END)\n",
    "\n",
    "\n",
    "###### Add the MemorySaver ######\n",
    "memory = MemorySaver()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ce03ae7",
   "metadata": {},
   "source": [
    "We compile the graph with a checkpointer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fdf7759b",
   "metadata": {},
   "outputs": [],
   "source": [
    "###### Add the interrupt_before ######\n",
    "graph = graph_builder.compile(checkpointer=memory)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41c54ff9",
   "metadata": {},
   "source": [
    "Let's visualize the graph!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "5ae46bba",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "###### Graph Visualization ######\n",
    "from IPython.display import Image, display\n",
    "\n",
    "try:\n",
    "    display(Image(graph.get_graph().draw_mermaid_png()))\n",
    "except Exception:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9dc0a002",
   "metadata": {},
   "source": [
    "## Adding Human Feedback\n",
    "\n",
    "Let's now perform **Human-in-the-loop** !"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "3f89a4e8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Please provide the latest news related to AI.\n",
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Please provide the latest news related to AI.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  search_keyword (call_JFu8CvoZ7QlIzVnKky1rMgCn)\n",
      " Call ID: call_JFu8CvoZ7QlIzVnKky1rMgCn\n",
      "  Args:\n",
      "    query: AI\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.runnables import RunnableConfig\n",
    "\n",
    "# User input\n",
    "question = \"Please provide the latest news related to AI.\"\n",
    "\n",
    "# Define the initial input State\n",
    "input = State(messages=[(\"user\", question)])\n",
    "\n",
    "# Config settings\n",
    "config = RunnableConfig(\n",
    "    # Visit up to 10 nodes; beyond this, a RecursionError will occur\n",
    "    recursion_limit=10,\n",
    "    # Set thread id\n",
    "    configurable={\"thread_id\": \"1\"},\n",
    "    # Set tags\n",
    "    tags=[\"06-LangGraph-Human-in-the-loop\"],\n",
    ")\n",
    "\n",
    "# Process events in a loop\n",
    "for event in graph.stream(\n",
    "    input=input,\n",
    "    config=config,\n",
    "    stream_mode=\"values\",\n",
    "    # Interrupt before executing the tools (stop before the tools node execution)\n",
    "    interrupt_before=[\"tools\"],\n",
    "):\n",
    "    for key, val in event.items():\n",
    "        # key is the node's name\n",
    "        # print(f\"\\n[{key}]\\n\")\n",
    "\n",
    "        # val(value) is the node's output\n",
    "        pretty_print_messages(val)\n",
    "\n",
    "        # State is stored in dict format within val(value)\n",
    "        if \"messages\" in val:\n",
    "            print(f\"Number of messages: {len(val['messages'])}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79194529",
   "metadata": {},
   "source": [
    "The chatbot invoked the tool, but its execution was interrupted.\n",
    "\n",
    "Let's check the graph state!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "86f4bd4d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('tools',)"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# snapshot of the graph state\n",
    "snapshot = graph.get_state(config)\n",
    "snapshot.next"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "486398fd",
   "metadata": {},
   "source": [
    "The reason for the interruption is that we set ```tools``` in ```interrupt_before``` while streaming the graph, causing the process to stop before executing the ```tools``` node. As a result, the next node ( ```.next``` ) becomes ```tools``` .\n",
    "\n",
    "Additionally, *in the previous tutorial* , ```.next``` was empty because the process had reached the final ```END``` node. However, with the interrupt set, ```.next``` is now set to ```tools``` ."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d9352b3",
   "metadata": {},
   "source": [
    "Now, let's check the last message in the snapshot!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "59aa479f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name': 'search_keyword',\n",
       "  'args': {'query': 'AI'},\n",
       "  'id': 'call_JFu8CvoZ7QlIzVnKky1rMgCn',\n",
       "  'type': 'tool_call'}]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "existing_message = snapshot.values[\"messages\"][-1]\n",
    "existing_message.tool_calls"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a36482dd",
   "metadata": {},
   "source": [
    "Next, let's resume the graph from the point where it was interrupted.\n",
    "\n",
    "**LangGraph** makes it easy to resume graph execution.\n",
    "\n",
    "Simply pass ```None``` as the ```input``` ."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "f88e20b3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  search_keyword (call_JFu8CvoZ7QlIzVnKky1rMgCn)\n",
      " Call ID: call_JFu8CvoZ7QlIzVnKky1rMgCn\n",
      "  Args:\n",
      "    query: AI\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: search_keyword\n",
      "\n",
      "[{\"url\": \"https://news.google.com/rss/articles/CBMikwFBVV95cUxNcllEYjZmTkNzUGpjenpxZ3N6ZlFOcFAxS2szNm80MWM4dVRSU3YzcGlzU0xrZkI3MEpBR3N2U19lSmdmVnR5R0dhT0U4S1RHR1JWZDlvTXd1MnU1TlNyZlRuYXVpS0cxY1pjckNaQU1URE9IV2kzZm96d2dYRWVUczBrYV9HUklmVDJNOElaTEVNMUHSAYoBQVVfeXFMTkttd0EzSUNJUnR5ak5UYWNfZWhpVHlrZFMxQ095TTVOb2JTX2RLSTZQV2dDNktvbTktR1B4bDBtRlpxUlJhck9mTGgzVDBzMXpTaXR6aEZ2VndnRElvaFNNcGE4VmtwTjdVcXVjYkU2SnlaUGZDSXRRUjZxb3RJSHFFcTZCVWpiaWJB?oc=5\", \"content\": \"Trump announces a $500 billion AI infrastructure investment in the US - CNN\"}, {\"url\": \"https://news.google.com/rss/articles/CBMinAFBVV95cUxOUFlLYjE5VkkyR0F4Vl9JYVVtSDl0Vk5hcHVLZ2h2bUgweE00NFplTFhDLWFBNDNpRlN0OXJiQ1JjSFNadmU2Z1BDaVpPMlYtTnBpS2VnQmR5dE9GZFB1c3hQR0NoNUo3ekpFTmFIWjI4TjdjV1ZrbDJ5LURaZ2s1UDhZSkd4cndaQ2pRRENscjNJc2JXZGZyazRmdDU?oc=5\", \"content\": \"Trump Announces $100 Billion A.I. Initiative - The New York Times\"}, {\"url\": \"https://news.google.com/rss/articles/CBMi1gFBVV95cUxNMXRqZnRNMTk2aGZUdlAydjY3aHktaHhDNkxqZi03MmtqUkp1OXg5YVY5WjdZZ2J0Zk5oY2ZWVW9ZR2tmdElleUhIdnppOHNDdXhYYnJ0RHFHaGJZbnNYTWJOaDJ6c3N0eWpyUTZCUV96NVFEYWlKQVdyZmpfdHFRWGpzNDVuME1XZDE3UnNrX3U1eEVaT0g4YnJFVHNsbFdQSVhpMmFWQmtCT1hib3c4ZHZsa0pxNGNGUTJJZVRSb2tXU3NwdzlCcl93ZnVuUXo3SGhTZ1ZB0gHbAUFVX3lxTFBXUDRJSEh3ZlhVV0xzSzExUFFvTkRlMG1XTmpUeUhHV1lJUUE4ME5tNFR0YXNKd05pc2pSSVZOTm1VbDhMMUxQbnBHbm5QRkRnb0lzWnM2Znk4cFVBendfcVlOdThtNnMyRHpHaXJYUXdxOVBSQXE0OV9QNjN1M2E4QVRaWS1JX2pVcV9qSndMQzFiYXpWVEE1d2s2RFJGZXF4RzFZd192Z2NpQWpfYTlPbENTenBRZm9ZWDV0NUZyYjJQS1d3SFNQQzlNTU1DdjJIUG5ac2RHazgxUQ?oc=5\", \"content\": \"Davos 2025: Trade, tariffs, AI and UN chief Guterres dominate World Economic Forum agenda - The Hill\"}, {\"url\": \"https://news.google.com/rss/articles/CBMilwFBVV95cUxPdXhCRVNEVzZYRGMxdVlwQkc4SXBmUmVUY0drTHQ5ZDk5dUdtdHhkSUJtRWowMG9kZkkzWVBFN2ZoWUFLeEdCUW00aDFmQkNWU0dYR0p4N0RoekxqQ0w3M1pQVU4wNC0yU1JRV3dGeW1hdW5oUEJiVTNKZ2Z5Y3ZSenFldjNpQmVTYjNSSW9tYi14LWx6enZV?oc=5\", \"content\": \"Google rushed to sell AI tools to Israel’s military after Hamas attack - The Washington Post\"}, {\"url\": \"https://news.google.com/rss/articles/CBMiswFBVV95cUxQdG9FV2tjQjZGeVdlQzFkOGZNdkd4bDYzVm1BZnVDT1B1bEc0UUpSWlVYbjdHcnl0bnpJOThhc3NlbGZBZ2tSSGh4RzJtdVVFNmVuLVYtOElxQjRLdnd3R0hoTjdmQmcyd1U4VHFsNWhGY0tTVmJhUkExaWJTQ2ZpVVpXQzd6VGw1TV9MVWxxalMxLWRkWnAwdjV4WG4xMVFTNmR5Nng4WFVxMGZiYkxiU3VVaw?oc=5\", \"content\": \"Trump Pushes to Make US an AI Superpower, With Fewer Guardrails - Bloomberg\"}]\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Here are the latest news articles related to AI:\n",
      "\n",
      "1. **Trump announces a $500 billion AI infrastructure investment in the US** - [CNN](https://news.google.com/rss/articles/CBMikwFBVV95cUxNcllEYjZmTkNzUGpjenpxZ3N6ZlFOcFAxS2szNm80MWM4dVRSU3YzcGlzU0xrZkI3MEpBR3N2U19lSmdmVnR5R0dhT0U4S1RHR1JWZDlvTXd1MnU1TlNyZlRuYXVpS0cxY1pjckNaQU1URE9IV2kzZm96d2dYRWVUczBrYV9HUklmVDJNOElaTEVNMUHSAYoBQVVfeXFMTkttd0EzSUNJUnR5ak5UYWNfZWhpVHlrZFMxQ095TTVOb2JTX2RLSTZQV2dDNktvbTktR1B4bDBtRlpxUlJhck9mTGgzVDBzMXpTaXR6aEZ2VndnRElvaFNNcGE4VmtwTjdVcXVjYkU2SnlaUGZDSXRRUjZxb3RJSHFFcTZCVWpiaWJB?oc=5)\n",
      "\n",
      "2. **Trump Announces $100 Billion A.I. Initiative** - [The New York Times](https://news.google.com/rss/articles/CBMinAFBVV95cUxOUFlLYjE5VkkyR0F4Vl9JYVVtSDl0Vk5hcHVLZ2h2bUgweE00NFplTFhDLWFBNDNpRlN0OXJiQ1JjSFNadmU2Z1BDaVpPMlYtTnBpS2VnQmR5dE9GZFB1c3hQR0NoNUo3ekpFTmFIWjI4TjdjV1ZrbDJ5LURaZ2s1UDhZSkd4cwdaQ2pRRENscjNJc2JXZGZyazRmdDU?oc=5)\n",
      "\n",
      "3. **Davos 2025: Trade, tariffs, AI and UN chief Guterres dominate World Economic Forum agenda** - [The Hill](https://news.google.com/rss/articles/CBMi1gFBVV95cUxNMXRqZnRNMTk2aGZUdlAydjY3aHktaHhDNkxqZi03MmtqUkp1OXg5YVY5WjdZZ2J0Zk5oY2ZWVW9ZR2tmdElleUhIdnppOHNDdXhYYnJ0RHFHaGJZbnNYTWJOaDJ6c3N0eWpyUTZCUV96NVFEYWlKQVdyZmpfdHFRWGpzNDVuME1XZDE3UnNrX3U1eEVaT0g4YnJFVHNsbFdQSVhpMmFWQmtCT1hib3c4ZHZsa0pxNGNGUTJJZVRSb2tXU3NwdzlCcl93ZnVuUXo3SGhTZ1ZB0gHbAUFVX3lxTFBXUDRJSEh3ZlhVV0xzSzExUFFvTkRlMG1XTmpUeUhHV1lJUUE4ME5tNFR0YXNKd05pc2pSSVZOTm1VbDhMMUxQbnBHbm5QRkRnb0lzWnM2Znk4cFVBendfcVlOdThtNnMyRHpHaXJYUXdxOVBSQXE0OV9QNjN1M2E4QVRaWS1JX2pVcV9qSndMQzFiYXpWVEE1d2s2RFJGZXF4RzFZd192Z2NpQWpfYTlPbENTenBRZm9ZWDV0NUZyYjJQS1d3SFNQQzlNTU1DdjJIUG5ac2RHazgxUQ?oc=5)\n",
      "\n",
      "4. **Google rushed to sell AI tools to Israel’s military after Hamas attack** - [The Washington Post](https://news.google.com/rss/articles/CBMilwFBVV95cUxPdXhCRVNEVzZYRGMxdVlwQkc4SXBmUmVUY0drTHQ5ZDk5dUdtdHhkSUJtRWowMG9kZkkzWVBFN2ZoWUFLeEdCUW00aDFmQkNWU0dYR0p4N0RoekxqQ0w3M1pQVU4wNC0yU1JRV3dGeW1hdW5oUEJiVTNKZ2Z5Y3ZSenFldjNpQmVTYjNSSW9tYi14LWx6enZV?oc=5)\n",
      "\n",
      "5. **Trump Pushes to Make US an AI Superpower, With Fewer Guardrails** - [Bloomberg](https://news.google.com/rss/articles/CBMiswFBVV95cUxQdG9FV2tjQjZGeVdlQzFkOGZNdkd4bDYzVm1BZnVDT1B1bEc0UUpSWlVYbjdHcnl0bnpJOThhc3NlbGZBZ2tSSGh4RzJtdVVFNmVuLVYtOElxQjRLdnd3R0hoTjdmQmcyd1U4VHFsNWhGY0tTVmJhUkExaWJTQ2ZpVVpXQzd6VGw1TV9MVWxxalMxLWRkWnAwdjV4WG4xMVFTNmR5Nng4WFVxMGZiYkxiU3VVaw?oc=5)\n",
      "\n",
      "Feel free to check the articles for more detailed information!\n"
     ]
    }
   ],
   "source": [
    "events = graph.stream(\n",
    "    input=None,  # Do not add anything to the current State\n",
    "    config=config,\n",
    "    stream_mode=\"values\",\n",
    ")\n",
    "\n",
    "# Process events in a loop\n",
    "for event in events:\n",
    "    if \"messages\" in event:\n",
    "        # Print the last message\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56c4b22e",
   "metadata": {},
   "source": [
    "We used **interrupt** to enable **human intervention** in the chatbot's execution."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "909696f2",
   "metadata": {},
   "source": [
    "Additionally, with the inclusion of a ```checkpointer``` , the graph can resume execution even after being indefinitely paused.\n",
    "\n",
    "Below is how you can use ```get_state_history``` to retrieve the state history.\n",
    "\n",
    "By specifying the desired state from the history, you can restart execution from that point."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "3a029ca0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Number of messages: 4 \n",
      "Next node: ()\n",
      "--------------------------------------------------------------------------------\n",
      "Number of messages: 3 \n",
      "Next node: ('chatbot',)\n",
      "--------------------------------------------------------------------------------\n",
      "Number of messages: 2 \n",
      "Next node: ('tools',)\n",
      "--------------------------------------------------------------------------------\n",
      "Number of messages: 1 \n",
      "Next node: ('chatbot',)\n",
      "--------------------------------------------------------------------------------\n",
      "Number of messages: 0 \n",
      "Next node: ('__start__',)\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "to_replay = None\n",
    "\n",
    "# Retrieve state history\n",
    "for state in graph.get_state_history(config):\n",
    "    print(\n",
    "        \"Number of messages:\", len(state.values[\"messages\"]), \"\\nNext node:\", state.next\n",
    "    )\n",
    "    print(\"-\" * 80)\n",
    "    if len(state.values[\"messages\"]) == 3:\n",
    "        to_replay = state"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca13bf6d",
   "metadata": {},
   "source": [
    "It is important to note that the ```checkpointer``` saves data at every step of the graph!\n",
    "\n",
    "The desired point is stored in ```to_replay``` . This allows you to specify the starting point for resuming execution.\n",
    "\n",
    "The ```checkpoint_id``` is stored in ```to_replay.config``` ."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "78d87058",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('chatbot',)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "{'configurable': {'thread_id': '1',\n",
       "  'checkpoint_ns': '',\n",
       "  'checkpoint_id': '1efd8a06-ace9-6516-8002-8731d8cb463e'}}"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(to_replay.next)\n",
    "display(to_replay.config)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e580548d",
   "metadata": {},
   "source": [
    "Using this ```checkpoint_id``` , LangGraph's checkpointer can load the state at that specific point. \n",
    "\n",
    "Note that the ```input``` must be set to ```None``` in this case."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "a5fb3c26",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: search_keyword\n",
      "\n",
      "[{\"url\": \"https://news.google.com/rss/articles/CBMikwFBVV95cUxNcllEYjZmTkNzUGpjenpxZ3N6ZlFOcFAxS2szNm80MWM4dVRSU3YzcGlzU0xrZkI3MEpBR3N2U19lSmdmVnR5R0dhT0U4S1RHR1JWZDlvTXd1MnU1TlNyZlRuYXVpS0cxY1pjckNaQU1URE9IV2kzZm96d2dYRWVUczBrYV9HUklmVDJNOElaTEVNMUHSAYoBQVVfeXFMTkttd0EzSUNJUnR5ak5UYWNfZWhpVHlrZFMxQ095TTVOb2JTX2RLSTZQV2dDNktvbTktR1B4bDBtRlpxUlJhck9mTGgzVDBzMXpTaXR6aEZ2VndnRElvaFNNcGE4VmtwTjdVcXVjYkU2SnlaUGZDSXRRUjZxb3RJSHFFcTZCVWpiaWJB?oc=5\", \"content\": \"Trump announces a $500 billion AI infrastructure investment in the US - CNN\"}, {\"url\": \"https://news.google.com/rss/articles/CBMinAFBVV95cUxOUFlLYjE5VkkyR0F4Vl9JYVVtSDl0Vk5hcHVLZ2h2bUgweE00NFplTFhDLWFBNDNpRlN0OXJiQ1JjSFNadmU2Z1BDaVpPMlYtTnBpS2VnQmR5dE9GZFB1c3hQR0NoNUo3ekpFTmFIWjI4TjdjV1ZrbDJ5LURaZ2s1UDhZSkd4cndaQ2pRRENscjNJc2JXZGZyazRmdDU?oc=5\", \"content\": \"Trump Announces $100 Billion A.I. Initiative - The New York Times\"}, {\"url\": \"https://news.google.com/rss/articles/CBMi1gFBVV95cUxNMXRqZnRNMTk2aGZUdlAydjY3aHktaHhDNkxqZi03MmtqUkp1OXg5YVY5WjdZZ2J0Zk5oY2ZWVW9ZR2tmdElleUhIdnppOHNDdXhYYnJ0RHFHaGJZbnNYTWJOaDJ6c3N0eWpyUTZCUV96NVFEYWlKQVdyZmpfdHFRWGpzNDVuME1XZDE3UnNrX3U1eEVaT0g4YnJFVHNsbFdQSVhpMmFWQmtCT1hib3c4ZHZsa0pxNGNGUTJJZVRSb2tXU3NwdzlCcl93ZnVuUXo3SGhTZ1ZB0gHbAUFVX3lxTFBXUDRJSEh3ZlhVV0xzSzExUFFvTkRlMG1XTmpUeUhHV1lJUUE4ME5tNFR0YXNKd05pc2pSSVZOTm1VbDhMMUxQbnBHbm5QRkRnb0lzWnM2Znk4cFVBendfcVlOdThtNnMyRHpHaXJYUXdxOVBSQXE0OV9QNjN1M2E4QVRaWS1JX2pVcV9qSndMQzFiYXpWVEE1d2s2RFJGZXF4RzFZd192Z2NpQWpfYTlPbENTenBRZm9ZWDV0NUZyYjJQS1d3SFNQQzlNTU1DdjJIUG5ac2RHazgxUQ?oc=5\", \"content\": \"Davos 2025: Trade, tariffs, AI and UN chief Guterres dominate World Economic Forum agenda - The Hill\"}, {\"url\": \"https://news.google.com/rss/articles/CBMilwFBVV95cUxPdXhCRVNEVzZYRGMxdVlwQkc4SXBmUmVUY0drTHQ5ZDk5dUdtdHhkSUJtRWowMG9kZkkzWVBFN2ZoWUFLeEdCUW00aDFmQkNWU0dYR0p4N0RoekxqQ0w3M1pQVU4wNC0yU1JRV3dGeW1hdW5oUEJiVTNKZ2Z5Y3ZSenFldjNpQmVTYjNSSW9tYi14LWx6enZV?oc=5\", \"content\": \"Google rushed to sell AI tools to Israel’s military after Hamas attack - The Washington Post\"}, {\"url\": \"https://news.google.com/rss/articles/CBMiswFBVV95cUxQdG9FV2tjQjZGeVdlQzFkOGZNdkd4bDYzVm1BZnVDT1B1bEc0UUpSWlVYbjdHcnl0bnpJOThhc3NlbGZBZ2tSSGh4RzJtdVVFNmVuLVYtOElxQjRLdnd3R0hoTjdmQmcyd1U4VHFsNWhGY0tTVmJhUkExaWJTQ2ZpVVpXQzd6VGw1TV9MVWxxalMxLWRkWnAwdjV4WG4xMVFTNmR5Nng4WFVxMGZiYkxiU3VVaw?oc=5\", \"content\": \"Trump Pushes to Make US an AI Superpower, With Fewer Guardrails - Bloomberg\"}]\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Here are the latest news articles related to AI:\n",
      "\n",
      "1. **Trump announces a $500 billion AI infrastructure investment in the US** - [CNN](https://news.google.com/rss/articles/CBMikwFBVV95cUxNcllEYjZmTkNzUGpjenpxZ3N6ZlFOcFAxS2szNm80MWM4dVRSU3YzcGlzU0xrZkI3MEpBR3N2U19lSmdmVnR5R0dhT0U4S1RHR1JWZDlvTXd1MnU1TlNyZlRuYXVpS0cxY1pjckNaQU1URE9IV2kzZm96d2dYRWVUczBrYV9HUklmVDJNOElaTEVNMUHSAYoBQVVfeXFMTkttd0EzSUNJUnR5ak5UYWNfZWhpVHlrZFMxQ095TTVOb2JTX2RLSTZQV2dDNktvbTktR1B4bDBtRlpxUlJhck9mTGgzVDBzMXpTaXR6aEZ2VndnRElvaFNNcGE4VmtwTjdVcXVjYkU2SnlaUGZDSXRRUjZxb3RJSHFFcTZCVWpiaWJB?oc=5)\n",
      "\n",
      "2. **Trump Announces $100 Billion A.I. Initiative** - [The New York Times](https://news.google.com/rss/articles/CBMinAFBVV95cUxOUFlLYjE5VkkyR0F4Vl9JYVVtSDl0Vk5hcHVLZ2h2bUgweE00NFplTFhDLWFBNDNpRlN0OXJiQ1JjSFNadmU2Z1BDaVpPMlYtTnBpS2VnQmR5dE9GZFB1c3hQR0NoNUo3ekpFTmFIWjI4TjdjV1ZrbDJ5LURaZ2s1UDhZSkd4cndaQ2pRRENscjNJc2JXZGZyazRmdDU?oc=5)\n",
      "\n",
      "3. **Davos 2025: Trade, tariffs, AI and UN chief Guterres dominate World Economic Forum agenda** - [The Hill](https://news.google.com/rss/articles/CBMi1gFBVV95cUxNMXRqZnRNMTk2aGZUdlAydjY3aHktaHhDNkxqZi03MmtqUkp1OXg5YVY5WjdZZ2J0Zk5oY2ZWVW9ZR2tmdElleUhIdnppOHNDdXhYYnJ0RHFHaGJZbnNYTWJOaDJ6c3N0eWpyUTZCUV96NVFEYWlKQVdyZmpfdHFRWGpzNDVuME1XZDE3UnNrX3U1eEVaT0g4YnJFVHNsbFdQSVhpMmFWQmtCT1hib3c4ZHZsa0pxNGNGUTJJZVRSb2tXU3NwdzlCcl93ZnVuUXo3SGhTZ1ZB0gHbAUFVX3lxTFBXUDRJSEh3ZlhVV0xzSzExUFFvTkRlMG1XTmpUeUhHV1lJUUE4ME5tNFR0YXNKd05pc2pSSVZOTm1VbDhMMUxQbnBHbm5QRkRnb0lzWnM2Znk4cFVBendfcVlOdThtNnMyRHpHaXJYUXdxOVBSQXE0OV9QNjN1M2E4QVRaWS1JX2pVcV9qSndMQzFiYXpWVEE1d2s2RFJGZXF4RzFZd192Z2NpQWpfYTlPbENTenBRZm9ZWDV0NUZyYjJQS1d3SFNQQzlNTU1DdjJIUG5ac2RHazgxUQ?oc=5)\n",
      "\n",
      "4. **Google rushed to sell AI tools to Israel’s military after Hamas attack** - [The Washington Post](https://news.google.com/rss/articles/CBMilwFBVV95cUxPdXhCRVNEVzZYRGMxdVlwQkc4SXBmUmVUY0drTHQ5ZDk5dUdtdHhkSUJtRWowMG9kZkkzWVBFN2ZoWUFLeEdCUW00aDFmQkNWU0dYR0p4N0RoekxqQ0w3M1pQVU4wNC0yU1JRV3dGeW1hdW5oUEJiVTNKZ2Z5Y3ZSenFldjNpQmVTYjNSSW9tYi14LWx6enZV?oc=5)\n",
      "\n",
      "5. **Trump Pushes to Make US an AI Superpower, With Fewer Guardrails** - [Bloomberg](https://news.google.com/rss/articles/CBMiswFBVV95cUxQdG9FV2tjQjZGeVdlQzFkOGZNdkd4bDYzVm1BZnVDT1B1bEc0UUpSWlVYbjdHcnl0bnpJOThhc3NlbGZBZ2tSSGh4RzJtdVVFNmVuLVYtOElxQjRLdnd3R0hoTjdmQmcyd1U4VHFsNWhGY0tTVmJhUkExaWJTQ2ZpVVpXQzd6VGw1TV9MVWxxalMxLWRkWnAwdjV4WG4xMVFTNmR5Nng4WFVxMGZiYkxiU3VVaw?oc=5)\n",
      "\n",
      "Feel free to check the articles for more detailed information!\n"
     ]
    }
   ],
   "source": [
    "for event in graph.stream(input=None, config=to_replay.config, stream_mode=\"values\"):\n",
    "    if \"messages\" in event:\n",
    "        # Print the last message\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
